﻿/*
 * Copyright (c) 2024 Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * -----------------------------------------------------------------------------
 *
 * $Date:       19. September 2024
 * $Revision:   V3.0
 *
 * Project:     I2C Driver for STMicroelectronics STM32 devices
 *
 * -----------------------------------------------------------------------------
 */

/*! \page i2c_stm32 I2C

# Revision History

- Version 3.0
  - Initial release

# Requirements

This driver requires the STM32 device specific **HAL** and **STM32CubeMX** (CubeMX) initialization code generator.
The driver instance is mapped to hardware as shown in the table below:

  CMSIS Driver Instance | STM32 Hardware Resource
  :---------------------|:-----------------------
  Driver_I2C1           | I2C1
  Driver_I2C2           | I2C2
  Driver_I2C3           | I2C3
  Driver_I2C4           | I2C4
  Driver_I2C5           | I2C5
  Driver_I2C6           | I2C6
  Driver_I2C7           | I2C7
  Driver_I2C8           | I2C8

# Deviations

This driver has the following deviations from the CMSIS-Driver specification:

__Conceptual__ deviations:
  - CubeMX generated initialization code (function MX_I2Cn_Init) already configures
    the peripheral. Power, clocks, pins, and interrupts are enabled after execution
    of initialization that executes in `main.c`.

__Functional__ deviations:
  - Initialize:
    - depends on the code generated by CubeMX.
    - does not initialize the pins.
  - Uninitialize:
    - does not de-initialize the pins.
  - PowerControl:
    - depends on the code generated by CubeMX.
    - low-power mode is not supported by HAL.
  - MasterTransmit and SlaveTransmit:
    - maximum number of data bytes supported by HAL with a single transmit request is 65535.
  - MasterReceive and SlaveReceive:
    - maximum number of data bytes supported by HAL with a single receive request is 65535.

# CubeMX Configuration

This driver requires the following configuration in CubeMX:

  - **clock**: **I2C** peripheral clock.
  - **peripheral**: **I2C** peripheral configured as **I2C** and **Parameter Settings** configured as desired.
  - **pins**: **I2C SCL** and **SDA pins**.
  - **DMA**: optional **DMA** configuration for transfers.
  - **interrupts**: enabled **I2C event** and **I2C error interrupts** and **IRQ handlers** that **Call HAL handlers** and
    enabled **DMA interrupts** and **IRQ handlers** that **Call HAL handlers**.

> **Notes**
>
> - configuration information in the **MX_Device.h** file is based on CubeMX configuration.
> - **DMA** configuration can differ between devices series so configure DMA **as required by the used device**.
> - for **DMA** usage on devices with cache, ensure that data buffers for Transmit and Receive functions
>   are in **non-cacheable memory**, or ensure that memory for transmit is updated (**cache clean**) before Transmit function
>   is called and that memory containing received data is updated after the reception finishes (**cache invalidate**).
> - some DMA controllers can only access specific memories, so ensure that proper memory is used for the buffers
>   according to the DMA requirement.

## Example

### Pinout & Configuration tab

  1. In the **Pinout view** window click on a pin and select it's functionality:
       Pin      | Functionality
       :--------|:--------------------:
       PB6      | **I2C1_SCL**
       PB7      | **I2C1_SDA**
     \n

  2. Under **Categories**: **Connectivity** select **I2C1**:

     __Mode__:
       - Mode: **I2C**

     __Configuration__:
       - Parameter Settings: as desired
       - GPIO Settings:
           Pin Name | Signal on Pin | Pin Context..| GPIO output..| GPIO mode                     | GPIO Pull-up/Pull..| Maximum out..| Fast Mode | User Label
           :--------|:-------------:|:------------:|:------------:|:-----------------------------:|:------------------:|:------------:|:---------:|:----------:
           PB6      | I2C1_SCL      | n/a          | n/a          | Alternate Function Push Pull  | Pull-up            | **Low**      | n/a       | .
           PB7      | I2C1_SDA      | n/a          | n/a          | Alternate Function Push Pull  | Pull-up            | **Low**      | n/a       | .
         \n

  3. Under **Categories**: **System Core** select **DMA**
     (might be different on other device series, or for some peripherals might be BDMA):

     __Configuration__:
       - DMA1, DMA2:

         Click on **Add** button and add DMA Requests as in table below:
           DMA Request | Stream         | Direction            | Priority
           :----------:|:--------------:|:---------------------|:--------
           I2C1_RX     | DMA1 Stream 0  | Peripheral to Memory | Low
           I2C1_TX     | DMA1 Stream 1  | Memory to Peripheral | Low
         \n

  4. Under **Categories**: **System Core** select **NVIC**:

     __Configuration__:
       - NVIC:
           NVIC Interrupt Table              | Enabled     | Preemption Priority | Sub Priority
           :---------------------------------|:-----------:|:-------------------:|:------------:
           DMA1 stream0 global interrupt     |   checked   | 0                   | 0
           DMA1 stream1 global interrupt     |   checked   | 0                   | 0
           I2C1 event interrupt              | **checked** | 0                   | 0
           I2C1 error interrupt              | **checked** | 0                   | 0
         \n

       - Code generation:
           Enabled interrupt table           | Select for..| Generate Enable in..| Generate IRQ h.. | Call HAL handler
           :---------------------------------|:-----------:|:-------------------:|:----------------:|:----------------:
           DMA1 stream0 global interrupt     | unchecked   | checked             | checked          | checked
           DMA1 stream1 global interrupt     | unchecked   | checked             | checked          | checked
           I2C1 event interrupt              | unchecked   | checked             | checked          | checked
           I2C1 error interrupt              | unchecked   | checked             | checked          | checked
         \n

### Clock Configuration tab

  1. Configure **To I2C1,2,3,5 (MHz)**: **100**

### Project Manager tab

  1. Under **Advanced Settings**:

     __Generated Function Calls__:
       Generate Code | Function Name               | Peripheral Inst..| Do not generate ..| Visibility (Static)
       :-------------|:---------------------------:|:----------------:|:-----------------:|:-------------------:
       checked       | MX_I2C1_Init                | I2C1             | unchecked         | checked

# Troubleshooting

## Compilation errors

### Error:    I2Cn peripheral clock frequency is unknown! {#i2c_periph_freq_unknown}

  - Reason:   peripheral clock frequency is not determinable from CubeMX configuration.
  - Solution: add **User Constant** with Name **I2Cn_PERIPH_CLOCK_FREQ** and Value of respective I2Cn **peripheral clock frequency (in Hz)**.
  - Example:  in the **Pinout & Configuration** tab, under **Categories**: **Connectivity** select **I2C1**, 
              under **Configuration**: **User Constants** add **Constant** with Name **I2C1_PERIPH_CLOCK_FREQ** and Value
              of **peripheral clock frequency (in Hz)** as determined from the **Clock Configuration** or device documentation.
*/

/*! \cond */

#include "I2C_STM32.h"

#include "RTE_Components.h"
#include  CMSIS_device_header

#include <string.h>

// Driver Version **************************************************************
                                                //  CMSIS Driver API version           , Driver version
static  const ARM_DRIVER_VERSION driver_version = { ARM_DRIVER_VERSION_MAJOR_MINOR(2,4), ARM_DRIVER_VERSION_MAJOR_MINOR(3,0) };
// *****************************************************************************

// Driver Capabilities *********************************************************
static const ARM_I2C_CAPABILITIES driver_capabilities = {
  1U,                           // 10-bit addressing supported
  0U                            // Reserved (must be zero)
};
// *****************************************************************************

// Compile-time configuration **************************************************

// Configuration depending on MX_Device.h

// Check if at least one peripheral instance is configured in STM32CubeMX
#if    (!defined(MX_I2C1) && \
        !defined(MX_I2C2) && \
        !defined(MX_I2C3) && \
        !defined(MX_I2C4) && \
        !defined(MX_I2C5) && \
        !defined(MX_I2C6) && \
        !defined(MX_I2C7) && \
        !defined(MX_I2C8))
#error  I2C driver requires at least one I2C peripheral configured in STM32CubeMX!

// Check if MX_Device.h version is as required (old version did not have all the necessary information)
#elif  (!defined(MX_DEVICE_VERSION) || (MX_DEVICE_VERSION < 0x01000000U))
#error  I2C driver requires new MX_Device.h configuration, please regenerate MX_Device.h file!

// Check if peripheral clock frequency is defined
#elif   defined(MX_I2C1) && !defined(MX_I2C1_PERIPH_CLOCK_FREQ)
#error  I2C1 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#elif   defined(MX_I2C2) && !defined(MX_I2C2_PERIPH_CLOCK_FREQ)
#error  I2C2 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#elif   defined(MX_I2C3) && !defined(MX_I2C3_PERIPH_CLOCK_FREQ)
#error  I2C3 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#elif   defined(MX_I2C4) && !defined(MX_I2C4_PERIPH_CLOCK_FREQ)
#error  I2C4 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#elif   defined(MX_I2C5) && !defined(MX_I2C5_PERIPH_CLOCK_FREQ)
#error  I2C5 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#elif   defined(MX_I2C6) && !defined(MX_I2C6_PERIPH_CLOCK_FREQ)
#error  I2C6 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#elif   defined(MX_I2C7) && !defined(MX_I2C7_PERIPH_CLOCK_FREQ)
#error  I2C7 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#elif   defined(MX_I2C8) && !defined(MX_I2C8_PERIPH_CLOCK_FREQ)
#error  I2C8 peripheral clock frequency is unknown! Consult the driver documentation at https://open-cmsis-pack.github.io/CMSIS-Driver_STM32/latest/i2c_stm32.html#i2c_periph_freq_unknown

#else
#define DRIVER_CONFIG_VALID             1
#endif

// Determine peripheral differences that driver needs to handle

// Determine if peripheral has filter features
// If MX_I2Cn_ANF_ENABLE or MX_I2Cn_DNF is defined then I2C has additional filter features
// configured by functions: HAL_I2CEx_ConfigAnalogFilter and HAL_I2CEx_ConfigDigitalFilter

#if    (defined(MX_I2C1_ANF_ENABLE) || defined(MX_I2C1_DNF) || \
        defined(MX_I2C2_ANF_ENABLE) || defined(MX_I2C2_DNF) || \
        defined(MX_I2C3_ANF_ENABLE) || defined(MX_I2C3_DNF) || \
        defined(MX_I2C4_ANF_ENABLE) || defined(MX_I2C4_DNF) || \
        defined(MX_I2C5_ANF_ENABLE) || defined(MX_I2C5_DNF) || \
        defined(MX_I2C6_ANF_ENABLE) || defined(MX_I2C6_DNF) || \
        defined(MX_I2C7_ANF_ENABLE) || defined(MX_I2C7_DNF) || \
        defined(MX_I2C8_ANF_ENABLE) || defined(MX_I2C8_DNF))
#define I2C_VARIANT_HAS_FILTER          1
#endif

// Determine if peripheral has TIMINGR register

#ifdef  I2C_TIMINGR_SCLL
#define I2C_VARIANT_TIMINGR             1
#endif

// *****************************************************************************

#ifdef  DRIVER_CONFIG_VALID     // Driver code is available only if configuration is valid

// Macros
// Macro for section for RW info
#ifdef  I2C_SECTION_NAME
#define I2Cn_SECTION_(name,n)   __attribute__((section(name #n)))
#define I2Cn_SECTION(n)         I2Cn_SECTION_(I2C_SECTION_NAME,n)
#else
#define I2Cn_SECTION(n)
#endif

#ifdef  I2C_VARIANT_HAS_FILTER  // If I2C peripheral has filters
// Macro to create i2c_ro_info and i2c_rw_info (for instances), with filter settings
#define INFO_DEFINE(n)                                                                                         \
extern  I2C_HandleTypeDef       hi2c##n;                                                                       \
static        RW_Info_t         i2c##n##_rw_info I2Cn_SECTION(n);                                              \
static  const RO_Info_t         i2c##n##_ro_info    = { &hi2c##n,                                              \
                                                        &i2c##n##_rw_info,                                     \
                                                         (uint32_t)MX_I2C##n##_PERIPH_CLOCK_FREQ,              \
                                                         { MX_I2C##n##_SCL_GPIOx,                              \
                                                           MX_I2C##n##_SCL_GPIO_Pin,                           \
                                                           MX_I2C##n##_SCL_GPIO_AF,                            \
                                                           MX_I2C##n##_SCL_GPIO_PuPd,                          \
                                                           MX_I2C##n##_SCL_GPIO_Speed                          \
                                                         },                                                    \
                                                         { MX_I2C##n##_SDA_GPIOx,                              \
                                                           MX_I2C##n##_SDA_GPIO_Pin,                           \
                                                           MX_I2C##n##_SDA_GPIO_AF,                            \
                                                           MX_I2C##n##_SDA_GPIO_PuPd,                          \
                                                           MX_I2C##n##_SDA_GPIO_Speed                          \
                                                         },                                                    \
                                                         MX_I2C##n##_ANF_ENABLE,                               \
                                                         MX_I2C##n##_DNF                                       \
                                                      };
#else
// Macro to create i2c_ro_info and i2c_rw_info (for instances), without filter settings
#define INFO_DEFINE(n)                                                                                         \
extern  I2C_HandleTypeDef       hi2c##n;                                                                       \
static        RW_Info_t         i2c##n##_rw_info I2Cn_SECTION(n);                                              \
static  const RO_Info_t         i2c##n##_ro_info    = { &hi2c##n,                                              \
                                                        &i2c##n##_rw_info,                                     \
                                                         (uint32_t)MX_I2C##n##_PERIPH_CLOCK_FREQ,              \
                                                         { MX_I2C##n##_SCL_GPIOx,                              \
                                                           MX_I2C##n##_SCL_GPIO_Pin,                           \
                                                           MX_I2C##n##_SCL_GPIO_AF,                            \
                                                           MX_I2C##n##_SCL_GPIO_PuPd,                          \
                                                           MX_I2C##n##_SCL_GPIO_Speed                          \
                                                         },                                                    \
                                                         { MX_I2C##n##_SDA_GPIOx,                              \
                                                           MX_I2C##n##_SDA_GPIO_Pin,                           \
                                                           MX_I2C##n##_SDA_GPIO_AF,                            \
                                                           MX_I2C##n##_SDA_GPIO_PuPd,                          \
                                                           MX_I2C##n##_SDA_GPIO_Speed                          \
                                                         }                                                     \
                                                      };
#endif

// Macro for declaring functions (for instances)
#define FUNCS_DECLARE(n)                                                                                       \
static  int32_t                 I2C##n##_Initialize      (ARM_I2C_SignalEvent_t cb_event);                     \
static  int32_t                 I2C##n##_Uninitialize    (void);                                               \
static  int32_t                 I2C##n##_PowerControl    (ARM_POWER_STATE state);                              \
static  int32_t                 I2C##n##_MasterTransmit  (uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending); \
static  int32_t                 I2C##n##_MasterReceive   (uint32_t addr,       uint8_t *data, uint32_t num, bool xfer_pending); \
static  int32_t                 I2C##n##_SlaveTransmit   (               const uint8_t *data, uint32_t num);   \
static  int32_t                 I2C##n##_SlaveReceive    (                     uint8_t *data, uint32_t num);   \
static  int32_t                 I2C##n##_GetDataCount    (void);                                               \
static  int32_t                 I2C##n##_Control         (uint32_t control, uint32_t arg);                     \
static  ARM_I2C_STATUS          I2C##n##_GetStatus       (void);

// Macro for defining functions (for instances)
#define FUNCS_DEFINE(n)                                                                                                                                                                                             \
static  int32_t                 I2C##n##_Initialize      (ARM_I2C_SignalEvent_t cb_event)                                       { return I2Cn_Initialize      (&i2c##n##_ro_info, cb_event); }                      \
static  int32_t                 I2C##n##_Uninitialize    (void)                                                                 { return I2Cn_Uninitialize    (&i2c##n##_ro_info); }                                \
static  int32_t                 I2C##n##_PowerControl    (ARM_POWER_STATE state)                                                { return I2Cn_PowerControl    (&i2c##n##_ro_info, state); }                         \
static  int32_t                 I2C##n##_MasterTransmit  (uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending)  { return I2Cn_MasterTransmit  (&i2c##n##_ro_info, addr, data, num, xfer_pending); } \
static  int32_t                 I2C##n##_MasterReceive   (uint32_t addr,       uint8_t *data, uint32_t num, bool xfer_pending)  { return I2Cn_MasterReceive   (&i2c##n##_ro_info, addr, data, num, xfer_pending); } \
static  int32_t                 I2C##n##_SlaveTransmit   (               const uint8_t *data, uint32_t num)                     { return I2Cn_SlaveTransmit   (&i2c##n##_ro_info, data, num); }                     \
static  int32_t                 I2C##n##_SlaveReceive    (                     uint8_t *data, uint32_t num)                     { return I2Cn_SlaveReceive    (&i2c##n##_ro_info, data, num); }                     \
static  int32_t                 I2C##n##_GetDataCount    (void)                                                                 { return I2Cn_GetDataCount    (&i2c##n##_ro_info); }                                \
static  int32_t                 I2C##n##_Control         (uint32_t control, uint32_t arg)                                       { return I2Cn_Control         (&i2c##n##_ro_info, control, arg); }                  \
static  ARM_I2C_STATUS          I2C##n##_GetStatus       (void)                                                                 { return I2Cn_GetStatus       (&i2c##n##_ro_info); }

// Macro for defining driver structures (for instances)
#define I2C_DRIVER(n)                   \
ARM_DRIVER_I2C Driver_I2C##n = {        \
  I2C_GetVersion,                       \
  I2C_GetCapabilities,                  \
  I2C##n##_Initialize,                  \
  I2C##n##_Uninitialize,                \
  I2C##n##_PowerControl,                \
  I2C##n##_MasterTransmit,              \
  I2C##n##_MasterReceive,               \
  I2C##n##_SlaveTransmit,               \
  I2C##n##_SlaveReceive,                \
  I2C##n##_GetDataCount,                \
  I2C##n##_Control,                     \
  I2C##n##_GetStatus                    \
};

// Transfer abort timeout definition
#ifndef I2C_XFER_ABORT_TIMEOUT
#define I2C_XFER_ABORT_TIMEOUT         (100U)           // I2C transfer abort timeout (in ms)
#endif

// Bus Clear clock period definition
#ifndef I2C_BUS_CLEAR_CLOCK_PERIOD
#define I2C_BUS_CLEAR_CLOCK_PERIOD     (2U)             // I2C bus clock period (in ms)
#endif

#ifdef  I2C_VARIANT_TIMINGR                             // If TIMINGR register exists

// TIMING register limit values
#define I2C_TIMINGR_PRESC_MAX          (1UL << 4)
#define I2C_TIMINGR_SCLDEL_MAX         (1UL << 4)
#define I2C_TIMINGR_SDADEL_MAX         (1UL << 4)
#define I2C_TIMINGR_SCLH_MAX           (1UL << 8)
#define I2C_TIMINGR_SCLL_MAX           (1UL << 8)

// Pulse width of spikes suppressed by the analog filter
#ifndef I2C_ANALOG_FILTER_DELAY_MIN
#define I2C_ANALOG_FILTER_DELAY_MIN     50U             // Min delay added by the analog filter (in ns)
#endif
#ifndef I2C_ANALOG_FILTER_DELAY_MAX
#define I2C_ANALOG_FILTER_DELAY_MAX     260U            // Max delay added by the analog filter (in ns)
#endif

// I2C standard timing specification
typedef struct {
  uint32_t                      clk_max;                // Max I2C speed (in Hz)
  uint32_t                      tf_max;                 // Max SDA and SCL fall time (in ns)
  uint32_t                      tr_max;                 // Max SDA and SCL rise time (in ns)
  uint32_t                      hddat_min;              // Min data hold time (in ns)
  uint32_t                      vddat_max;              // Max data valid time (in ns)
  uint32_t                      sudat_min;              // Min data setup time (in ns)
  uint32_t                      scll_min;               // Min SCL low period (in ns)
  uint32_t                      sclh_min;               // Min SCL high period (in ns)
} StandardTiming_t;

// I2C clock setup auxiliary structure
typedef struct {
  uint16_t                      i2cclk;                 // Kernel clock period (in ns)
  uint16_t                      busclk;                 // Bus clock period (in ns)
  uint16_t                      dfd;                    // Digital filter delay (in ns)
  uint16_t                      afd_min;                // Analog filter min delay (in ns)
  uint16_t                      afd_max;                // Analog filter max delay (in ns)
  uint16_t                      error;                  // Clock setup error (ns)
} ClockSetup_t;

// I2C peripheral TIMING register values
typedef struct {
  uint8_t                       presc;                  // Timing register value PRESC[3:0]
  uint8_t                       scldel;                 // Timing register value SCLDEL[3:0]
  uint8_t                       sdadel;                 // Timing register value SDADEL[3:0]
  uint8_t                       sclh;                   // Timing register value SCLH[7:0]
  uint8_t                       scll;                   // Timing register value SCLL[7:0]
} TimingReg_t;

#endif // I2C_VARIANT_TIMINGR

// Pin configuration
typedef struct {
  GPIO_TypeDef                 *ptr_port;               // Pointer to pin's port
  uint16_t                      pin;                    // Pin
  uint16_t                      af;                     // Pin's alternate function setting
  uint16_t                      pull;                   // Pin's pull-up/pull-down setting
  uint16_t                      speed;                  // Pin's speed setting
} PinConfig_t;

// Driver status
typedef struct {
  uint8_t                       initialized  : 1;       // Initialized status: 0 - not initialized, 1 - initialized
  uint8_t                       powered      : 1;       // Power status:       0 - not powered,     1 - powered
  uint8_t                       reserved     : 6;       // Reserved (for padding)
} DriverStatus_t;

// Instance run-time information (RW)
typedef struct {
  ARM_I2C_SignalEvent_t         cb_event;               // Event callback
  DriverStatus_t                drv_status;             // Driver status
  volatile uint8_t              i2c_direction;          // I2C Direction: 0=Transmitter, 1=Receiver
  volatile uint8_t              i2c_general_call;       // I2C General Call indication (cleared on start of next Slave operation)
  volatile uint8_t              i2c_arbitration_lost;   // I2C Master lost arbitration (cleared on start of next Master operation)
  volatile uint8_t              i2c_bus_error;          // I2C Bus error detected (cleared on start of next Master/Slave operation)
           uint8_t              xfer_no_stop;           // Transfer not generating STOP: 0 - not generating STOP, not 0 - generating STOP
  volatile uint8_t              xfer_abort;             // Transfer abort done: 0 - abort not done might be in progress, not 0 - abort done
           uint8_t              reserved;               // Reserved (for padding)
           uint32_t             xfer_size;              // Requested transfer size (in bytes)
  const    uint8_t * volatile   slave_xfer_tx_data;     // Pointer to transmit data (for Slave only)
           uint8_t * volatile   slave_xfer_rx_data;     // Pointer to receive  data (for Slave only)
           uint16_t             slave_xfer_tx_num;      // Requested number of bytes to transmit
           uint16_t             slave_xfer_rx_num;      // Requested number of bytes to receive
} RW_Info_t;

// Instance compile-time information (RO)
// also contains pointer to run-time information
typedef struct {
  I2C_HandleTypeDef            *ptr_hi2c;               // Pointer to I2C handle
  RW_Info_t                    *ptr_rw_info;            // Pointer to run-time information (RW)
  uint32_t                      peri_clock_freq;        // Peripheral clock frequency (in Hz)
  PinConfig_t                   scl_pin_config;         // SCL pin configuration structure
  PinConfig_t                   sda_pin_config;         // SDA pin configuration structure
#ifdef I2C_VARIANT_HAS_FILTER
  uint16_t                      anf_en;                 // Analog noise filter enable
  uint16_t                      dnf;                    // Digital noise filter coefficient value (0 - disabled)
#endif
} RO_Info_t;

// Information definitions (for instances)
#ifdef MX_I2C1
INFO_DEFINE(1)
#endif
#ifdef MX_I2C2
INFO_DEFINE(2)
#endif
#ifdef MX_I2C3
INFO_DEFINE(3)
#endif
#ifdef MX_I2C4
INFO_DEFINE(4)
#endif
#ifdef MX_I2C5
INFO_DEFINE(5)
#endif
#ifdef MX_I2C6
INFO_DEFINE(6)
#endif
#ifdef MX_I2C7
INFO_DEFINE(7)
#endif
#ifdef MX_I2C8
INFO_DEFINE(8)
#endif

#ifdef  I2C_VARIANT_TIMINGR             // If TIMINGR register exists

// Common I2C standard timing specification
static const StandardTiming_t i2c_spec_standard = {
  100000U,      // clk_max
  300U,         // tf_max
  1000U,        // tr_max
  0U,           // hddat_min
  3450U,        // vddat_max
  250U,         // sudat_min
  4700U,        // scll_min
  4000U,        // sclh_min
};

static const StandardTiming_t i2c_spec_fast = {
  400000U,      // clk_max
  300U,         // tf_max
  300U,         // tr_max
  0U,           // hddat_min
  900U,         // vddat_max
  100U,         // sudat_min
  1300U,        // scll_min
  600U,         // sclh_min
};

static const StandardTiming_t i2c_spec_fast_plus = {
  1000000U,     // clk_max
  100U,         // tf_max
  120U,         // tr_max
  0U,           // hddat_min
  450U,         // vddat_max
  50U,          // sudat_min
  500U,         // scll_min
  260U,         // sclh_min
};

#endif // I2C_VARIANT_TIMINGR

// List of available I2C instance infos
static const RO_Info_t * const i2c_ro_info_list[] = {
#ifdef MX_I2C1
  &i2c1_ro_info,
#endif
#ifdef MX_I2C2
  &i2c2_ro_info,
#endif
#ifdef MX_I2C3
  &i2c3_ro_info,
#endif
#ifdef MX_I2C4
  &i2c4_ro_info,
#endif
#ifdef MX_I2C5
  &i2c5_ro_info,
#endif
#ifdef MX_I2C6
  &i2c6_ro_info,
#endif
#ifdef MX_I2C7
  &i2c7_ro_info,
#endif
#ifdef MX_I2C8
  &i2c8_ro_info,
#endif
  NULL
};

// Local functions prototypes
static const RO_Info_t         *I2C_GetInfo         (const I2C_HandleTypeDef * const hi2c);
static uint32_t                 I2Cn_GetPeriphClock (const RO_Info_t * const ptr_ro_info);
#ifdef I2C_VARIANT_TIMINGR      // If TIMINGR register exists
static int32_t                  I2Cn_GetSCLRatio    (ClockSetup_t *ptr_clock_setup, const StandardTiming_t *ptr_timing_spec, TimingReg_t *ptr_timing_reg);
static uint32_t                 I2Cn_GetTimingValue (ClockSetup_t *ptr_clock_setup, const StandardTiming_t *ptr_timing_spec);
#endif
static ARM_DRIVER_VERSION       I2C_GetVersion      (void);
static ARM_I2C_CAPABILITIES     I2C_GetCapabilities (void);
static int32_t                  I2Cn_Initialize     (const RO_Info_t * const ptr_ro_info, ARM_I2C_SignalEvent_t cb_event);
static int32_t                  I2Cn_Uninitialize   (const RO_Info_t * const ptr_ro_info);
static int32_t                  I2Cn_PowerControl   (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state);
static int32_t                  I2Cn_MasterTransmit (const RO_Info_t * const ptr_ro_info, uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending);
static int32_t                  I2Cn_MasterReceive  (const RO_Info_t * const ptr_ro_info, uint32_t addr,       uint8_t *data, uint32_t num, bool xfer_pending);
static int32_t                  I2Cn_SlaveTransmit  (const RO_Info_t * const ptr_ro_info,                const uint8_t *data, uint32_t num);
static int32_t                  I2Cn_SlaveReceive   (const RO_Info_t * const ptr_ro_info,                      uint8_t *data, uint32_t num);
static int32_t                  I2Cn_GetDataCount   (const RO_Info_t * const ptr_ro_info);
static int32_t                  I2Cn_Control        (const RO_Info_t * const ptr_ro_info, uint32_t control, uint32_t arg);
static ARM_I2C_STATUS           I2Cn_GetStatus      (const RO_Info_t * const ptr_ro_info);

// Local driver functions declarations (for instances)
#ifdef MX_I2C1
FUNCS_DECLARE(1)
#endif
#ifdef MX_I2C2
FUNCS_DECLARE(2)
#endif
#ifdef MX_I2C3
FUNCS_DECLARE(3)
#endif
#ifdef MX_I2C4
FUNCS_DECLARE(4)
#endif
#ifdef MX_I2C5
FUNCS_DECLARE(5)
#endif
#ifdef MX_I2C6
FUNCS_DECLARE(6)
#endif
#ifdef MX_I2C7
FUNCS_DECLARE(7)
#endif
#ifdef MX_I2C8
FUNCS_DECLARE(8)
#endif

// Auxiliary functions

/**
  \fn          RO_Info_t *I2C_GetInfo (const I2C_HandleTypeDef * const hi2c)
  \brief       Get pointer to RO_Info_t structure corresponding to specified hi2c.
  \param[in]   hi2c     Pointer to I2C handle structure (I2C_HandleTypeDef)
  \return      pointer to I2C RO info structure (RO_Info_t)
*/
static const RO_Info_t *I2C_GetInfo (const I2C_HandleTypeDef * const hi2c) {
  const RO_Info_t *ptr_ro_info;
        uint8_t    i;

  ptr_ro_info = NULL;
  i            = 0U;

  // Find I2C which uses same hi2c handle as parameter hi2c
  for (i = 0U; i < (sizeof(i2c_ro_info_list)/sizeof(RO_Info_t *)); i++) {
    if (i2c_ro_info_list[i] != NULL) {
      if (i2c_ro_info_list[i]->ptr_hi2c == hi2c) {
        ptr_ro_info = i2c_ro_info_list[i];
        break;
      }
    }
  }

  return ptr_ro_info;
}

/**
  \fn          uint32_t I2Cn_GetPeriphClock (const RO_Info_t * const ptr_ro_info)
  \brief       Get peripheral clock frequency.
  \param[in]   ptr_ro_info     Pointer to I2C RO info structure (RO_Info_t)
  \return      frequency in Hz
*/
static uint32_t I2Cn_GetPeriphClock (const RO_Info_t * const ptr_ro_info) {
  return ptr_ro_info->peri_clock_freq;
}

#ifdef  I2C_VARIANT_TIMINGR             // If TIMINGR register exists
/**
  \fn          int32_t I2Cn_GetSCLRatio (ClockSetup_t *ptr_clock_setup, StandardTiming_t *ptr_timing_spec, TimingReg_t *ptr_timing_reg)
  \brief       Evaluate SCL low/high ratio.
  \param[in]   ptr_clock_setup    Pointer to clock setup structure
  \param[in]   ptr_timing_spec    Pointer to standard timing specification structure
  \param[in]   ptr_timing_reg     Pointer to Timing register values
  \return      SCL period error
*/
static int32_t I2Cn_GetSCLRatio (ClockSetup_t *ptr_clock_setup, const StandardTiming_t *ptr_timing_spec, TimingReg_t *ptr_timing_reg) {
  uint32_t clk_max, clk_min;
  uint32_t tpresc, tsync;
  uint32_t tscl, tscll, tsclh;
  uint32_t scll, sclh;
  int32_t  err;

  // Set minimum bus clock frequency to 80% of max
  clk_min = (ptr_timing_spec->clk_max * 80) / 100;

  // Convert values to ns
  clk_max = 1000000000U / clk_min;
  clk_min = 1000000000U / ptr_timing_spec->clk_max;
  tsync   = ptr_clock_setup->afd_min + ptr_clock_setup->dfd + (2 * ptr_clock_setup->i2cclk);
  tpresc  = (ptr_timing_reg->presc + 1U) * ptr_clock_setup->i2cclk;

  err = 0;

  // Evaluate all values of SCLL and SCLH
  for (scll = 0U; scll < I2C_TIMINGR_SCLL_MAX; scll++) {
    tscll = ((scll + 1U) * tpresc) + tsync;

    if ((tscll >= ptr_timing_spec->scll_min) && (ptr_clock_setup->i2cclk < ((tscll - (ptr_clock_setup->afd_min + ptr_clock_setup->dfd)) / 4))) {
      // SCL low meets specification

      for (sclh = 0U; sclh < I2C_TIMINGR_SCLH_MAX; sclh++) {
        tsclh = ((sclh + 1U) * tpresc) + tsync;

        if ((tsclh >= ptr_timing_spec->sclh_min) && (tsclh > ptr_clock_setup->i2cclk)) {
          // SCL high meets specification
          tscl = tscll + tsclh;

          if ((tscl >= clk_min) && (tscl <= clk_max)) {
            // Evaluate SCL period error
            err = (int32_t)(tscl - ptr_clock_setup->busclk);

            if (err >= 0) {
              ptr_timing_reg->sclh = (uint8_t)sclh;
              ptr_timing_reg->scll = (uint8_t)scll;
              return (err);
            }
          }
        }
      }
    }
  }

  // No solution
  return -1;
}

/**
  \fn          uint32_t I2Cn_GetTimingValue (ClockSetup_t *ptr_clock_setup, const StandardTiming_t *ptr_timing_spec)
  \brief       Determine TIMINGR register settings based on input structures.
  \param[in]   ptr_clock_setup    Pointer to clock setup structure
  \param[in]   ptr_timing_spec    Pointer to standard timing specification structure
  \return      TIMINGR register value
*/
static uint32_t I2Cn_GetTimingValue (ClockSetup_t *ptr_clock_setup, const StandardTiming_t *ptr_timing_spec) {
  TimingReg_t timing_reg;
  uint32_t    dnf_en;
  uint32_t    presc;
  uint32_t    sdadel, sdadel_min, sdadel_max;
  uint32_t    scldel, scldel_min;
  uint32_t    p, l, a;
  uint32_t    timing;
  int32_t     val, err;

  // Set digital noise filter enabled flag
  if (ptr_clock_setup->dfd > 0U) {
    dnf_en = 1U;
  } else {
    dnf_en = 0U;
  }

  // SDADEL (max)
  val = (int32_t)(ptr_timing_spec->vddat_max - (ptr_timing_spec->tr_max + ptr_clock_setup->afd_max + ((dnf_en + 4) * ptr_clock_setup->i2cclk)));

  if (val > 0) {
    sdadel_max = (uint32_t)val;
  } else {
    sdadel_max = 0U;
  }

  // SDADEL (min)
  val = (int32_t)(ptr_timing_spec->tf_max + ptr_timing_spec->hddat_min - (ptr_clock_setup->afd_min + ((dnf_en + 3) * ptr_clock_setup->i2cclk)));

  if (val > 0) {
    sdadel_min = (uint32_t)val;
  } else {
    sdadel_min = 0U;
  }

  // SCLDEL (min)
  scldel_min = ptr_timing_spec->tr_max + ptr_timing_spec->sudat_min;

  // Set timing register max value
  timing = 0xF0FFFFFFU;

  // Evaluate all values of PRESC, SCLDEL and SDADEL
  for (p = 0U; p < I2C_TIMINGR_PRESC_MAX; p++) {
    presc = (p + 1U) * ptr_clock_setup->i2cclk;

    for (l = 0U; l < I2C_TIMINGR_SCLDEL_MAX; l++) {
      // tSCLDEL = (SCLDEL + 1U) * ((PRESC + 1U) * tI2CCLK)
      scldel = (l + 1U) * presc;

      if (scldel >= scldel_min) {
        // SCLDEL is above low limit, evaluate SDADEL
        for (a = 0U; a < I2C_TIMINGR_SDADEL_MAX; a++) {
          // tSDADEL = SDADEL * ((PRESC + 1U) * tI2CCLK)
          sdadel = a * presc;

          if ((sdadel >= sdadel_min) && (sdadel <= sdadel_max)) {
            // Valid presc (p), scldel (l) and sdadel (a)
            timing_reg.presc  = (uint8_t)p;
            timing_reg.scldel = (uint8_t)l;
            timing_reg.sdadel = (uint8_t)a;

            // Determine SCLL and SCLH values
            err = I2Cn_GetSCLRatio(ptr_clock_setup, ptr_timing_spec, &timing_reg);

            if (err >= 0) {
              if (err < ptr_clock_setup->error) {
                // Truncate error since it will never be bigger than 16-bit
                ptr_clock_setup->error = (uint16_t)err;

                // Save timing settings
                timing  = (timing_reg.scll   & 0xFFU);
                timing |= (timing_reg.sclh   & 0xFFU) <<  8;
                timing |= (timing_reg.sdadel & 0x0FU) << 16;
                timing |= (timing_reg.scldel & 0x0FU) << 20;
                timing |= (timing_reg.presc  & 0x0FU) << 28;
              }
            }
          }
        }
      }
    }
  }

  return timing;
}
#endif

// Driver functions ************************************************************

/**
  \fn          ARM_DRIVER_VERSION I2C_GetVersion (void)
  \brief       Get driver version.
  \return      \ref ARM_DRIVER_VERSION
*/
static ARM_DRIVER_VERSION I2C_GetVersion (void) {
  return driver_version;
}

/**
  \fn          ARM_I2C_CAPABILITIES I2C_GetCapabilities (void)
  \brief       Get driver capabilities.
  \return      \ref ARM_I2C_CAPABILITIES
*/
static ARM_I2C_CAPABILITIES I2C_GetCapabilities (void) {
  return driver_capabilities;
}

/**
  \fn          int32_t I2Cn_Initialize (const RO_Info_t * const ptr_ro_info, ARM_I2C_SignalEvent_t cb_event)
  \brief       Initialize I2C Interface.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \param[in]   cb_event      Pointer to \ref ARM_I2C_SignalEvent
  \return      \ref execution_status
*/
static int32_t I2Cn_Initialize (const RO_Info_t * const ptr_ro_info, ARM_I2C_SignalEvent_t cb_event) {

  // Clear run-time info
  memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

  // Register callback function
  ptr_ro_info->ptr_rw_info->cb_event = cb_event;

  // Set driver status to initialized
  ptr_ro_info->ptr_rw_info->drv_status.initialized = 1U;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2Cn_Uninitialize (const RO_Info_t * const ptr_ro_info)
  \brief       De-initialize I2C Interface.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \return      \ref execution_status
*/
static int32_t I2Cn_Uninitialize (const RO_Info_t * const ptr_ro_info) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered != 0U) {
    // If peripheral is powered, power off the peripheral
    (void)I2Cn_PowerControl(ptr_ro_info, ARM_POWER_OFF);
  }

  // Clear run-time info
  memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2Cn_PowerControl (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state)
  \brief       Control I2C Interface Power.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \param[in]   state           Power state
  \return      \ref execution_status
*/
static int32_t I2Cn_PowerControl (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state) {
  ARM_I2C_SignalEvent_t cb_event;
  DriverStatus_t        drv_status;

  switch (state) {
    case ARM_POWER_FULL:
      if (ptr_ro_info->ptr_rw_info->drv_status.initialized == 0U) {
        return ARM_DRIVER_ERROR;
      }

      // Store variables we need to preserve
      cb_event   = ptr_ro_info->ptr_rw_info->cb_event;
      drv_status = ptr_ro_info->ptr_rw_info->drv_status;

      // Clear run-time info
      memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

      // Restore variables we wanted to preserve
      ptr_ro_info->ptr_rw_info->cb_event   = cb_event;
      ptr_ro_info->ptr_rw_info->drv_status = drv_status;

      // Initialize pins, clocks, interrupts and peripheral
      if (HAL_I2C_Init(ptr_ro_info->ptr_hi2c) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

#ifdef I2C_VARIANT_HAS_FILTER           // If I2C peripheral has filters
      // Reconfigure Analog Noise Filter because HAL_I2C_Init destroys ANFOFF setting in the CR1 register
      if (ptr_ro_info->anf_en != 0U) {
        if (HAL_I2CEx_ConfigAnalogFilter(ptr_ro_info->ptr_hi2c, I2C_ANALOGFILTER_ENABLE) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }
      } else {
        if (HAL_I2CEx_ConfigAnalogFilter(ptr_ro_info->ptr_hi2c, I2C_ANALOGFILTER_DISABLE) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }
      }

      // Reconfigure Digital Noise Filter because HAL_I2C_Init destroys DNF setting in the CR1 register
      if (HAL_I2CEx_ConfigDigitalFilter(ptr_ro_info->ptr_hi2c, ptr_ro_info->dnf) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }
#endif

      // Set driver status to powered
      ptr_ro_info->ptr_rw_info->drv_status.powered = 1U;
      break;

    case ARM_POWER_OFF:

      // If data transfer operation is in progress, abort it
      if (I2Cn_GetStatus(ptr_ro_info).busy != 0U) {
        (void)I2Cn_Control(ptr_ro_info, ARM_I2C_ABORT_TRANSFER, 0U);
      }

      // De-initialize pins, clocks, interrupts and peripheral
      (void)HAL_I2C_DeInit(ptr_ro_info->ptr_hi2c);

      // Set driver status to not powered
      ptr_ro_info->ptr_rw_info->drv_status.powered = 0U;

      // Store variables we need to preserve
      cb_event   = ptr_ro_info->ptr_rw_info->cb_event;
      drv_status = ptr_ro_info->ptr_rw_info->drv_status;

      // Clear run-time info
      memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

      // Restore variables we wanted to preserve
      ptr_ro_info->ptr_rw_info->cb_event   = cb_event;
      ptr_ro_info->ptr_rw_info->drv_status = drv_status;
      break;

    case ARM_POWER_LOW:
      return ARM_DRIVER_ERROR_UNSUPPORTED;

    default:
      return ARM_DRIVER_ERROR_PARAMETER;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2Cn_MasterTransmit (const RO_Info_t * const ptr_ro_info, uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending)
  \brief       Start transmitting data as I2C Master.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \param[in]   addr          Slave address (7-bit or 10-bit)
  \param[in]   data          Pointer to buffer with data to transmit to I2C Slave
  \param[in]   num           Number of data bytes to transmit
  \param[in]   xfer_pending  Transfer operation is pending - Stop condition will not be generated
  \return      \ref execution_status
*/
static int32_t I2Cn_MasterTransmit (const RO_Info_t * const ptr_ro_info, uint32_t addr, const uint8_t *data, uint32_t num, bool xfer_pending) {
  HAL_StatusTypeDef tx_status;
  uint16_t          saddr;
  uint32_t          opt;
  int32_t           ret;

  if ((data == NULL) || (num == 0U) || (num > (uint32_t)UINT16_MAX) ||
     ((addr & ~((uint32_t)ARM_I2C_ADDRESS_10BIT | (uint32_t)ARM_I2C_ADDRESS_GC)) > 0x3FFU)) {
    // If any parameter is invalid
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (I2Cn_GetStatus(ptr_ro_info).busy != 0U) {
    return ARM_DRIVER_ERROR_BUSY;
  }

  saddr = (addr & 0x3FFU);
  if (ptr_ro_info->ptr_hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) {
    saddr <<= 1;
  }

  ptr_ro_info->ptr_rw_info->i2c_direction        = 0U;
  ptr_ro_info->ptr_rw_info->i2c_bus_error        = 0U;
  ptr_ro_info->ptr_rw_info->i2c_arbitration_lost = 0U;

  ptr_ro_info->ptr_rw_info->xfer_size            = num;

  if (xfer_pending == true) {           // If transfer should not generate STOP at the end
    if (ptr_ro_info->ptr_rw_info->xfer_no_stop == 0U) {
      // First transfer without STOP generation
      ptr_ro_info->ptr_rw_info->xfer_no_stop = 1U;
      opt = I2C_FIRST_FRAME;
    } else {
      // Any further transfer without STOP generation
      opt = I2C_OTHER_FRAME;
    }
  } else {                              // If transfer should generate STOP at the end
    if (ptr_ro_info->ptr_rw_info->xfer_no_stop == 1U) {
      // If previous request did not generate STOP after transfer, terminate the sequence
      ptr_ro_info->ptr_rw_info->xfer_no_stop = 0U;
      opt = I2C_OTHER_AND_LAST_FRAME;
    } else {
      opt = I2C_LAST_FRAME;
    }
  }

  // Start the transmit
  if (ptr_ro_info->ptr_hi2c->hdmatx != NULL) {  // If DMA is used for Tx
    tx_status = HAL_I2C_Master_Seq_Transmit_DMA(ptr_ro_info->ptr_hi2c, saddr, (uint8_t *)(uint32_t)data, (uint16_t)num, opt);
  } else {                                      // If DMA is not configured (IRQ mode)
    tx_status = HAL_I2C_Master_Seq_Transmit_IT (ptr_ro_info->ptr_hi2c, saddr, (uint8_t *)(uint32_t)data, (uint16_t)num, opt);
  }

  // Convert HAL status code to CMSIS-Driver status code
  switch (tx_status) {
    case HAL_ERROR:
      ret = ARM_DRIVER_ERROR;
      break;

    case HAL_BUSY:
      ret = ARM_DRIVER_ERROR_BUSY;
      break;

    case HAL_OK:
      ret = ARM_DRIVER_OK;
      break;

    case HAL_TIMEOUT:
    default:
      ret = ARM_DRIVER_ERROR;
      break;
  }

  return ret;
}

/**
  \fn          int32_t I2Cn_MasterReceive (const RO_Info_t * const ptr_ro_info, uint32_t addr, uint8_t *data, uint32_t num, bool xfer_pending)
  \brief       Start receiving data as I2C Master.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \param[in]   addr          Slave address (7-bit or 10-bit)
  \param[out]  data          Pointer to buffer for data to receive from I2C Slave
  \param[in]   num           Number of data bytes to receive
  \param[in]   xfer_pending  Transfer operation is pending - Stop condition will not be generated
  \return      \ref execution_status
*/
static int32_t I2Cn_MasterReceive (const RO_Info_t * const ptr_ro_info, uint32_t addr, uint8_t *data, uint32_t num, bool xfer_pending) {
  HAL_StatusTypeDef rx_status;
  uint16_t          saddr;
  uint32_t          opt;
  int32_t           ret;

  if ((data == NULL) || (num == 0U) || (num > (uint32_t)UINT16_MAX) ||
     ((addr & ~((uint32_t)ARM_I2C_ADDRESS_10BIT | (uint32_t)ARM_I2C_ADDRESS_GC)) > 0x3FFU)) {
    // If any parameter is invalid
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (I2Cn_GetStatus(ptr_ro_info).busy != 0U) {
    return ARM_DRIVER_ERROR_BUSY;
  }

  saddr = (addr & 0x3FFU);
  if (ptr_ro_info->ptr_hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) {
    saddr <<= 1;
  }

  ptr_ro_info->ptr_rw_info->i2c_direction        = 1U;
  ptr_ro_info->ptr_rw_info->i2c_bus_error        = 0U;
  ptr_ro_info->ptr_rw_info->i2c_arbitration_lost = 0U;

  ptr_ro_info->ptr_rw_info->xfer_size            = num;

  if (xfer_pending == true) {           // If transfer should not generate STOP at the end
    if (ptr_ro_info->ptr_rw_info->xfer_no_stop == 0U) {
      // First transfer without STOP generation
      ptr_ro_info->ptr_rw_info->xfer_no_stop = 1U;
      opt = I2C_FIRST_FRAME;
    } else {
      // Any further transfer without STOP generation
      opt = I2C_OTHER_FRAME;
    }
  } else {                              // If transfer should generate STOP at the end
    if (ptr_ro_info->ptr_rw_info->xfer_no_stop == 1U) {
      // If previous request did not generate STOP after transfer, terminate the sequence
      ptr_ro_info->ptr_rw_info->xfer_no_stop = 0U;
      opt = I2C_OTHER_AND_LAST_FRAME;
    } else {
      opt = I2C_LAST_FRAME;
    }
  }

  // Start the reception
  if (ptr_ro_info->ptr_hi2c->hdmarx != NULL) {  // If DMA is used for Rx
    rx_status = HAL_I2C_Master_Seq_Receive_DMA(ptr_ro_info->ptr_hi2c, saddr, data, (uint16_t)num, opt);
  } else {                                      // If DMA is not configured (IRQ mode)
    rx_status = HAL_I2C_Master_Seq_Receive_IT (ptr_ro_info->ptr_hi2c, saddr, data, (uint16_t)num, opt);
  }

  // Convert HAL status code to CMSIS-Driver status code
  switch (rx_status) {
    case HAL_ERROR:
      ret = ARM_DRIVER_ERROR;
      break;

    case HAL_BUSY:
      ret = ARM_DRIVER_ERROR_BUSY;
      break;

    case HAL_OK:
      ret = ARM_DRIVER_OK;
      break;

    case HAL_TIMEOUT:
    default:
      ret = ARM_DRIVER_ERROR;
      break;
  }

  return ret;
}

/**
  \fn          int32_t I2Cn_SlaveTransmit (const RO_Info_t * const ptr_ro_info, const uint8_t *data, uint32_t num)
  \brief       Start transmitting data as I2C Slave.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \param[in]   data          Pointer to buffer with data to transmit to I2C Master
  \param[in]   num           Number of data bytes to transmit
  \return      \ref execution_status
*/
static int32_t I2Cn_SlaveTransmit (const RO_Info_t * const ptr_ro_info, const uint8_t *data, uint32_t num) {

  if ((data == NULL) || (num == 0U) || (num > (uint32_t)UINT16_MAX)) {
    // If any parameter is invalid
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (I2Cn_GetStatus(ptr_ro_info).busy != 0U) {
    return ARM_DRIVER_ERROR_BUSY;
  }

  if (HAL_I2C_GetState(ptr_ro_info->ptr_hi2c) != HAL_I2C_STATE_LISTEN) {
    return ARM_DRIVER_ERROR;
  }

  ptr_ro_info->ptr_rw_info->i2c_bus_error       = 0U;
  ptr_ro_info->ptr_rw_info->i2c_general_call    = 0U;

  ptr_ro_info->ptr_rw_info->xfer_size           = 0U;

  // Just register transmit parameters, actual operation will be started from HAL_I2C_AddrCallback
  ptr_ro_info->ptr_rw_info->slave_xfer_tx_data  = data;
  ptr_ro_info->ptr_rw_info->slave_xfer_tx_num   = (uint16_t)num;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2Cn_SlaveReceive (const RO_Info_t * const ptr_ro_info, uint8_t *data, uint32_t num)
  \brief       Start receiving data as I2C Slave.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \param[out]  data          Pointer to buffer for data to receive from I2C Master
  \param[in]   num           Number of data bytes to receive
  \return      \ref execution_status
*/
static int32_t I2Cn_SlaveReceive (const RO_Info_t * const ptr_ro_info, uint8_t *data, uint32_t num) {

  if ((data == NULL) || (num == 0U) || (num > (uint32_t)UINT16_MAX)) {
    // If any parameter is invalid
    return ARM_DRIVER_ERROR_PARAMETER;
  }

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (I2Cn_GetStatus(ptr_ro_info).busy != 0U) {
    return ARM_DRIVER_ERROR_BUSY;
  }

  ptr_ro_info->ptr_rw_info->i2c_bus_error       = 0U;
  ptr_ro_info->ptr_rw_info->i2c_general_call    = 0U;

  ptr_ro_info->ptr_rw_info->xfer_size           = 0U;

  // Just register receive parameters, actual operation will be started from HAL_I2C_AddrCallback
  ptr_ro_info->ptr_rw_info->slave_xfer_rx_data  = data;
  ptr_ro_info->ptr_rw_info->slave_xfer_rx_num   = (uint16_t)num;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t I2Cn_GetDataCount (const RO_Info_t * const ptr_ro_info)
  \brief       Get transferred data count.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \return      number of data bytes transferred; -1 when Slave is not addressed by Master
*/
static int32_t I2Cn_GetDataCount (const RO_Info_t * const ptr_ro_info) {
  int32_t cnt;
  int32_t cnt_xferred;
  uint8_t cnt_xferred_valid;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return 0;
  }

  if (HAL_I2C_GetState(ptr_ro_info->ptr_hi2c) == HAL_I2C_STATE_LISTEN) {
    cnt = -1;
  } else {
    cnt_xferred       = 0;
    cnt_xferred_valid = 0U;
    if (ptr_ro_info->ptr_hi2c->hdmarx != NULL) {        // If DMA is used for Rx
      if (ptr_ro_info->ptr_hi2c->hdmarx->State == HAL_DMA_STATE_BUSY) {
        // If reception is in progress
        cnt_xferred       = (int32_t)__HAL_DMA_GET_COUNTER(ptr_ro_info->ptr_hi2c->hdmarx);
        cnt_xferred_valid = 1U;
      }
    }
    if ((ptr_ro_info->ptr_hi2c->hdmatx != NULL) &&      // If DMA is used for Tx
        (cnt_xferred_valid == 0U)) {                    // and not valid
      if (ptr_ro_info->ptr_hi2c->hdmatx->State == HAL_DMA_STATE_BUSY) {
        // If transmission is in progress
        cnt_xferred       = (int32_t)__HAL_DMA_GET_COUNTER(ptr_ro_info->ptr_hi2c->hdmatx);
        cnt_xferred_valid = 1U;
      }
    }
    if (cnt_xferred_valid == 0U) {
      // If DMA is not used and in progress
      cnt_xferred = (int32_t)ptr_ro_info->ptr_hi2c->XferCount;
    }

    cnt = (int32_t)ptr_ro_info->ptr_rw_info->xfer_size - cnt_xferred;
  }

  return cnt;
}

/**
  \fn          int32_t I2Cn_Control (const RO_Info_t * const ptr_ro_info, uint32_t control, uint32_t arg)
  \brief       Control I2C Interface.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \param[in]   control       Operation
  \param[in]   arg           Argument of operation (optional)
  \return      \ref execution_status
*/
static int32_t I2Cn_Control (const RO_Info_t * const ptr_ro_info, uint32_t control, uint32_t arg) {
        HAL_I2C_ModeTypeDef mode;
        GPIO_InitTypeDef    GPIO_InitStruct;
        GPIO_PinState       state;
        uint32_t            i;
        uint32_t            periph_clk;
#ifdef  I2C_VARIANT_TIMINGR             // If TIMINGR register exists
  const StandardTiming_t   *ptr_std_timing;
        ClockSetup_t        clock_setup;
        uint32_t            scl_freq;
#endif

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  // Special handling for Abort Transfer command
  if (control == ARM_I2C_ABORT_TRANSFER) {
    mode = HAL_I2C_GetMode(ptr_ro_info->ptr_hi2c);
    switch (mode) {
      case HAL_I2C_MODE_NONE:           // No I2C communication on going
        break;

      case HAL_I2C_MODE_MASTER:         // I2C communication is in Master Mode
        ptr_ro_info->ptr_rw_info->xfer_abort = 0U;

        if (HAL_I2C_Master_Abort_IT(ptr_ro_info->ptr_hi2c, (uint16_t)ptr_ro_info->ptr_hi2c->Init.OwnAddress1) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }

        // Wait until abort operation completes
        for (i = 0U; i < I2C_XFER_ABORT_TIMEOUT; i++) {
          if (ptr_ro_info->ptr_rw_info->xfer_abort == 0U) {
            // If abort finished, exit the loop => success
            break;
          }
          HAL_Delay(1U);
        }
        if (i == I2C_XFER_ABORT_TIMEOUT) {
          // If abort did not finish before timeout => failed
          return ARM_DRIVER_ERROR;
        }
        break;

      case HAL_I2C_MODE_SLAVE:          // I2C communication is in Slave Mode
        // Generate NACK when in Slave mode
#ifdef  I2C_VARIANT_TIMINGR             // If this is variant with TIMINGR register
        __HAL_I2C_GENERATE_NACK(ptr_ro_info->ptr_hi2c);
#else                                   // if this is I2C peripheral without TIMINGR register
        ptr_ro_info->ptr_hi2c->Instance->CR1 &= ~I2C_CR1_ACK;
#endif
        break;

      case HAL_I2C_MODE_MEM:            // I2C communication is in Memory Mode
      default:
        return ARM_DRIVER_ERROR;
    }

    return ARM_DRIVER_OK;
  }

  // Check if peripheral is busy
  if (I2Cn_GetStatus(ptr_ro_info).busy != 0U) {
    return ARM_DRIVER_ERROR_BUSY;
  }

  switch (control) {
    case ARM_I2C_OWN_ADDRESS:                   // Set Own Slave Address; arg = address
      if (arg == 0U) {                          // Disable Slave
        if (HAL_I2C_DisableListen_IT(ptr_ro_info->ptr_hi2c) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }
      } else {                                  // Set Slave address
        if ((arg & ARM_I2C_ADDRESS_GC) != 0U) {
          // Enable general call
          ptr_ro_info->ptr_hi2c->Init.GeneralCallMode = I2C_GENERALCALL_ENABLE;
        } else {
          // Disable general call
          ptr_ro_info->ptr_hi2c->Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
        }

        if ((arg & ARM_I2C_ADDRESS_10BIT) != 0U) {
          // Own address is a 10-bit address
          ptr_ro_info->ptr_hi2c->Init.AddressingMode = I2C_ADDRESSINGMODE_10BIT;
        } else {
          // Own address is a 7-bit address
          ptr_ro_info->ptr_hi2c->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
        }

        ptr_ro_info->ptr_hi2c->Init.OwnAddress1 = (arg << 1) & 0x03FFU;

        // Update the Own Address configuration
        if (HAL_I2C_Init(ptr_ro_info->ptr_hi2c) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }

#ifdef  I2C_VARIANT_HAS_FILTER                  // If I2C peripheral has filters
        // Reconfigure Analog Noise Filter because HAL_I2C_Init destroys ANFOFF setting in the CR1 register
        if (ptr_ro_info->anf_en != 0U) {
          if (HAL_I2CEx_ConfigAnalogFilter(ptr_ro_info->ptr_hi2c, I2C_ANALOGFILTER_ENABLE) != HAL_OK) {
            return ARM_DRIVER_ERROR;
          }
        } else {
          if (HAL_I2CEx_ConfigAnalogFilter(ptr_ro_info->ptr_hi2c, I2C_ANALOGFILTER_DISABLE) != HAL_OK) {
            return ARM_DRIVER_ERROR;
          }
        }

        // Reconfigure Digital Noise Filter because HAL_I2C_Init destroys DNF setting in the CR1 register
        if (HAL_I2CEx_ConfigDigitalFilter(ptr_ro_info->ptr_hi2c, ptr_ro_info->dnf) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }
#endif

        // Enable the Address listen mode
        if (HAL_I2C_EnableListen_IT(ptr_ro_info->ptr_hi2c) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }
      }
      break;

    case ARM_I2C_BUS_SPEED:                     // Set Bus Speed; arg = speed
      periph_clk = I2Cn_GetPeriphClock(ptr_ro_info);
      if (periph_clk == 0U) {
        // If peripheral clock is unknown
        return ARM_DRIVER_ERROR_UNSUPPORTED;
      }

#ifdef  I2C_VARIANT_TIMINGR                     // If TIMINGR register exists
      switch (arg) {
        case ARM_I2C_BUS_SPEED_STANDARD:        // Standard Speed (100kHz)
          ptr_std_timing = &i2c_spec_standard;
          scl_freq = 100000U;
          break;

        case ARM_I2C_BUS_SPEED_FAST:            // Fast Speed (400kHz)
          ptr_std_timing = &i2c_spec_fast;
          scl_freq = 400000U;
          break;

        case ARM_I2C_BUS_SPEED_FAST_PLUS:       // Fast+ Speed (1MHz)
          ptr_std_timing = &i2c_spec_fast_plus;
          scl_freq = 1000000U;
          break;

        default:
          return ARM_DRIVER_ERROR_UNSUPPORTED;
      }

      // Determine peripheral and bus clock period (in ns)
      clock_setup.i2cclk = (uint16_t)((1000000000U + (periph_clk / 2)) / periph_clk);
      clock_setup.busclk = (uint16_t)((1000000000U + (scl_freq   / 2)) / scl_freq);

      // Determine digital filter delay (in ns)
      clock_setup.dfd = clock_setup.i2cclk * ((ptr_ro_info->ptr_hi2c->Instance->CR1 & I2C_CR1_DNF) >> 8);

      // Set analog filter delay (in ns)
      if (ptr_ro_info->anf_en != 0U) {
        clock_setup.afd_min = I2C_ANALOG_FILTER_DELAY_MIN;
        clock_setup.afd_max = I2C_ANALOG_FILTER_DELAY_MAX;
      } else {
        clock_setup.afd_min = 0U;
        clock_setup.afd_max = 0U;
      }

      // Set max iteration error
      clock_setup.error = 0xFFFF;

      // Get TIMINGR register values
      ptr_ro_info->ptr_hi2c->Init.Timing = I2Cn_GetTimingValue(&clock_setup, ptr_std_timing);
#else                                   // If this is I2C peripheral without filter capabilities
      ptr_ro_info->ptr_hi2c->Init.ClockSpeed = arg;
#endif

      // Update the bus speed configuration
      if (HAL_I2C_Init(ptr_ro_info->ptr_hi2c) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

#ifdef  I2C_VARIANT_HAS_FILTER          // If I2C peripheral has filters
      // Reconfigure Analog Noise Filter because HAL_I2C_Init destroys ANFOFF setting in the CR1 register
      if (ptr_ro_info->anf_en != 0U) {
        if (HAL_I2CEx_ConfigAnalogFilter(ptr_ro_info->ptr_hi2c, I2C_ANALOGFILTER_ENABLE) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }
      } else {
        if (HAL_I2CEx_ConfigAnalogFilter(ptr_ro_info->ptr_hi2c, I2C_ANALOGFILTER_DISABLE) != HAL_OK) {
          return ARM_DRIVER_ERROR;
        }
      }

      // Reconfigure Digital Noise Filter because HAL_I2C_Init destroys DNF setting in the CR1 register
      if (HAL_I2CEx_ConfigDigitalFilter(ptr_ro_info->ptr_hi2c, ptr_ro_info->dnf) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }
#endif
      break;

    case ARM_I2C_BUS_CLEAR:
      // Configure SCL pin as GPIO
      GPIO_InitStruct.Pin       = ptr_ro_info->scl_pin_config.pin;
      GPIO_InitStruct.Mode      = GPIO_MODE_OUTPUT_OD;
      GPIO_InitStruct.Pull      = ptr_ro_info->scl_pin_config.pull;
      GPIO_InitStruct.Speed     = ptr_ro_info->scl_pin_config.speed;
      GPIO_InitStruct.Alternate = ptr_ro_info->scl_pin_config.af;
      HAL_GPIO_Init(ptr_ro_info->scl_pin_config.ptr_port, &GPIO_InitStruct);

      // Configure SDA pin as GPIO
      GPIO_InitStruct.Pin       = ptr_ro_info->sda_pin_config.pin;
      GPIO_InitStruct.Mode      = GPIO_MODE_OUTPUT_OD;
      GPIO_InitStruct.Pull      = ptr_ro_info->sda_pin_config.pull;
      GPIO_InitStruct.Speed     = ptr_ro_info->sda_pin_config.speed;
      GPIO_InitStruct.Alternate = ptr_ro_info->sda_pin_config.af;
      HAL_GPIO_Init(ptr_ro_info->sda_pin_config.ptr_port, &GPIO_InitStruct);

      // Pull SCL and SDA lines high
      HAL_GPIO_WritePin(ptr_ro_info->scl_pin_config.ptr_port, ptr_ro_info->scl_pin_config.pin, GPIO_PIN_SET);
      HAL_GPIO_WritePin(ptr_ro_info->sda_pin_config.ptr_port, ptr_ro_info->sda_pin_config.pin, GPIO_PIN_SET);

      HAL_Delay(I2C_BUS_CLEAR_CLOCK_PERIOD);

      for (i = 0U; i < 9U; i++) {
        if (HAL_GPIO_ReadPin(ptr_ro_info->sda_pin_config.ptr_port, ptr_ro_info->sda_pin_config.pin) == GPIO_PIN_SET) {
          // Break from loop if Slave released SDA line
          break;
        }
        // Drive SCL clock line high
        HAL_GPIO_WritePin(ptr_ro_info->scl_pin_config.ptr_port, ptr_ro_info->scl_pin_config.pin, GPIO_PIN_SET);
        HAL_Delay(I2C_BUS_CLEAR_CLOCK_PERIOD/2);

        // Drive SCL clock line low
        HAL_GPIO_WritePin(ptr_ro_info->scl_pin_config.ptr_port, ptr_ro_info->scl_pin_config.pin, GPIO_PIN_RESET);
        HAL_Delay(I2C_BUS_CLEAR_CLOCK_PERIOD/2);
      }

      // Check SDA line state
      state = HAL_GPIO_ReadPin(ptr_ro_info->sda_pin_config.ptr_port, ptr_ro_info->sda_pin_config.pin);

      // Configure SCL pin as I2C pin
      GPIO_InitStruct.Pin       = ptr_ro_info->scl_pin_config.pin;
      GPIO_InitStruct.Mode      = GPIO_MODE_AF_OD;
      GPIO_InitStruct.Pull      = ptr_ro_info->scl_pin_config.pull;
      GPIO_InitStruct.Speed     = ptr_ro_info->scl_pin_config.speed;
      GPIO_InitStruct.Alternate = ptr_ro_info->scl_pin_config.af;
      HAL_GPIO_Init(ptr_ro_info->scl_pin_config.ptr_port, &GPIO_InitStruct);

      // Configure SDA pin as I2C pin
      GPIO_InitStruct.Pin       = ptr_ro_info->sda_pin_config.pin;
      GPIO_InitStruct.Mode      = GPIO_MODE_AF_OD;
      GPIO_InitStruct.Pull      = ptr_ro_info->sda_pin_config.pull;
      GPIO_InitStruct.Speed     = ptr_ro_info->sda_pin_config.speed;
      GPIO_InitStruct.Alternate = ptr_ro_info->sda_pin_config.af;
      HAL_GPIO_Init(ptr_ro_info->sda_pin_config.ptr_port, &GPIO_InitStruct);

      if (ptr_ro_info->ptr_rw_info->cb_event != NULL) {
        ptr_ro_info->ptr_rw_info->cb_event(ARM_I2C_EVENT_BUS_CLEAR);
      }

      if (state == GPIO_PIN_RESET) {
        // If Bus Clear has failed as Slave left SDA line low
        return ARM_DRIVER_ERROR;
      }

      return ARM_DRIVER_OK;

    default:
      return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          ARM_I2C_STATUS I2Cn_GetStatus (const RO_Info_t * const ptr_ro_info)
  \brief       Get I2C status.
  \param[in]   ptr_ro_info   Pointer to I2C RO info structure (RO_Info_t)
  \return      I2C status \ref ARM_I2C_STATUS
*/
static ARM_I2C_STATUS I2Cn_GetStatus (const RO_Info_t * const ptr_ro_info) {
  ARM_I2C_STATUS status;

  // Clear status structure
  memset(&status, 0, sizeof(ARM_I2C_STATUS));

  // Process HAL state
  switch (HAL_I2C_GetState(ptr_ro_info->ptr_hi2c)) {
    case HAL_I2C_STATE_BUSY:            // An internal process is ongoing
    case HAL_I2C_STATE_BUSY_RX:         // Data Reception process is ongoing
    case HAL_I2C_STATE_ABORT:           // Abort user request ongoing
    case HAL_I2C_STATE_BUSY_TX:         // Data Transmission process is ongoing
    case HAL_I2C_STATE_BUSY_TX_LISTEN:  // Address Listen Mode and Data Transmission process is ongoing
      status.busy = 1U;
      break;

    case HAL_I2C_STATE_LISTEN:          // Address Listen Mode is ongoing
    case HAL_I2C_STATE_BUSY_RX_LISTEN:  // Address Listen Mode and Data Reception process is ongoing
      // Listen is not considered busy from driver perspective

    case HAL_I2C_STATE_RESET:           // Peripheral is not yet Initialized
    case HAL_I2C_STATE_READY:           // Peripheral Initialized and ready for use
    default:
      // Not busy related
      break;
  }

  // Determine mode
  if (HAL_I2C_GetMode(ptr_ro_info->ptr_hi2c) == HAL_I2C_MODE_MASTER) {
    status.mode = 1U;
  }

  // Process additionally handled communication information
  if (ptr_ro_info->ptr_rw_info->i2c_direction != 0U) {
    status.direction = 1U;
  }
  if (ptr_ro_info->ptr_rw_info->i2c_general_call != 0U) {
    status.general_call = 1U;
  }
  if (ptr_ro_info->ptr_rw_info->i2c_arbitration_lost != 0U) {
    status.arbitration_lost = 1U;
  }
  if (ptr_ro_info->ptr_rw_info->i2c_bus_error != 0U) {
    status.bus_error = 1U;
  }

  return status;
}

// HAL callback functions ******************************************************

/**
  \fn          void HAL_I2C_MasterTxCpltCallback (I2C_HandleTypeDef *hi2c)
  \brief       Master Tx Transfer completed callback.
  \param[in]   hi2c   Pointer to a I2C_HandleTypeDef structure that contains
  *                   the configuration information for the specified I2C
  */
void HAL_I2C_MasterTxCpltCallback (I2C_HandleTypeDef *hi2c) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info->cb_event == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->cb_event(ARM_I2C_EVENT_TRANSFER_DONE);
}

/**
  \fn          void HAL_I2C_MasterRxCpltCallback (I2C_HandleTypeDef *hi2c)
  \brief       Master Rx Transfer completed callback.
  \param[in]   hi2c   Pointer to a I2C_HandleTypeDef structure that contains
  *                   the configuration information for the specified I2C
  */
void HAL_I2C_MasterRxCpltCallback (I2C_HandleTypeDef *hi2c) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info->cb_event == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->cb_event(ARM_I2C_EVENT_TRANSFER_DONE);
}

/**
  \fn          void HAL_I2C_AddrCallback (I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
  \brief       Slave Address Match callback.
  \param[in]   hi2c                 Pointer to a I2C_HandleTypeDef structure that contains
  *                                 the configuration information for the specified I2C
  \param[in]   TransferDirection    Master request Transfer Direction (Write/Read), value of @ref I2C_XFERDIRECTION
  \param[in]   AddrMatchCode        Address Match Code
  */
void HAL_I2C_AddrCallback (I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) {
  const RO_Info_t *ptr_ro_info;
        uint32_t   event;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  event = 0U;

  if (TransferDirection == I2C_DIRECTION_TRANSMIT) {            // If Master requests to send data to Slave
    if (ptr_ro_info->ptr_rw_info->slave_xfer_rx_data != NULL) { // If Slave operation was registered by SlaveReceive
      // Start the Slave reception
      ptr_ro_info->ptr_rw_info->xfer_size = ptr_ro_info->ptr_rw_info->slave_xfer_rx_num;
      if (ptr_ro_info->ptr_hi2c->hdmarx != NULL) {              // If DMA is used for Rx
        (void)HAL_I2C_Slave_Seq_Receive_DMA(ptr_ro_info->ptr_hi2c, ptr_ro_info->ptr_rw_info->slave_xfer_rx_data, ptr_ro_info->ptr_rw_info->slave_xfer_rx_num, I2C_NEXT_FRAME);
      } else {                                                  // If DMA is not configured (IRQ mode)
        (void)HAL_I2C_Slave_Seq_Receive_IT (ptr_ro_info->ptr_hi2c, ptr_ro_info->ptr_rw_info->slave_xfer_rx_data, ptr_ro_info->ptr_rw_info->slave_xfer_rx_num, I2C_NEXT_FRAME);
      }
    } else {                                                    // If Slave operation was not registered by SlaveReceive
      event = ARM_I2C_EVENT_SLAVE_RECEIVE;
      ptr_ro_info->ptr_rw_info->i2c_direction = 1U;
    }
  } else {                                                      // If Master requests to receive data from Slave
    if (ptr_ro_info->ptr_rw_info->slave_xfer_tx_data != NULL) { // If Slave operation was registered by SlaveTransmit
      // Start the Slave transmission
      ptr_ro_info->ptr_rw_info->xfer_size = ptr_ro_info->ptr_rw_info->slave_xfer_tx_num;
      if (ptr_ro_info->ptr_hi2c->hdmatx != NULL) {              // If DMA is used for Tx
        (void)HAL_I2C_Slave_Seq_Transmit_DMA(ptr_ro_info->ptr_hi2c, (uint8_t *)(uint32_t)ptr_ro_info->ptr_rw_info->slave_xfer_tx_data, ptr_ro_info->ptr_rw_info->slave_xfer_tx_num, I2C_NEXT_FRAME);
      } else {                                          // If DMA is not configured (IRQ mode)
        (void)HAL_I2C_Slave_Seq_Transmit_IT (ptr_ro_info->ptr_hi2c, (uint8_t *)(uint32_t)ptr_ro_info->ptr_rw_info->slave_xfer_tx_data, ptr_ro_info->ptr_rw_info->slave_xfer_tx_num, I2C_NEXT_FRAME);
      }
    } else {                                            // If Slave operation was not registered by SlaveTransmit
      event = ARM_I2C_EVENT_SLAVE_TRANSMIT;
      ptr_ro_info->ptr_rw_info->i2c_direction = 0U;
    }
  }

  if ((event != 0U) && (AddrMatchCode == 0U)) {
    // General call address
    event |= ARM_I2C_EVENT_GENERAL_CALL;
    ptr_ro_info->ptr_rw_info->i2c_general_call = 1U;
  }

  if (ptr_ro_info->ptr_rw_info->cb_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_event(event);
  }

  if (event != 0U) {
#ifdef  I2C_VARIANT_TIMINGR             // If this is variant with TIMINGR register
    __HAL_I2C_GENERATE_NACK(ptr_ro_info->ptr_hi2c);
    __HAL_I2C_ENABLE_IT(ptr_ro_info->ptr_hi2c, I2C_IT_ADDRI | I2C_IT_STOPI | I2C_IT_NACKI | I2C_IT_ERRI);
#else                                   // if this is I2C peripheral without TIMINGR register
    ptr_ro_info->ptr_hi2c->Instance->CR1 &= ~I2C_CR1_ACK;
    __HAL_I2C_ENABLE_IT(ptr_ro_info->ptr_hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR);
#endif
  }
}

/**
  \fn          void HAL_I2C_SlaveTxCpltCallback (I2C_HandleTypeDef *hi2c)
  \brief       Slave Tx Transfer completed callback.
  \param[in]   hi2c   Pointer to a I2C_HandleTypeDef structure that contains
  *                   the configuration information for the specified I2C
  */
void HAL_I2C_SlaveTxCpltCallback (I2C_HandleTypeDef *hi2c) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->slave_xfer_tx_data = NULL;

  if (ptr_ro_info->ptr_rw_info->cb_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_event(ARM_I2C_EVENT_TRANSFER_DONE);
  }

  // Re-enable interrupts
#ifdef  I2C_VARIANT_TIMINGR             // If this is variant with TIMINGR register
  __HAL_I2C_ENABLE_IT(ptr_ro_info->ptr_hi2c, I2C_IT_ADDRI | I2C_IT_STOPI | I2C_IT_NACKI | I2C_IT_ERRI);
#else                                   // if this is I2C peripheral without TIMINGR register
  __HAL_I2C_ENABLE_IT(ptr_ro_info->ptr_hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR);
#endif
}

/**
  \fn          void HAL_I2C_SlaveRxCpltCallback (I2C_HandleTypeDef *hi2c)
  \brief       Slave Rx Transfer completed callback.
  \param[in]   hi2c   Pointer to a I2C_HandleTypeDef structure that contains
  *                   the configuration information for the specified I2C
  */
void HAL_I2C_SlaveRxCpltCallback (I2C_HandleTypeDef *hi2c) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->slave_xfer_rx_data = NULL;

  if (ptr_ro_info->ptr_rw_info->cb_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_event(ARM_I2C_EVENT_TRANSFER_DONE);
  }

  // Re-enable interrupts
#ifdef  I2C_VARIANT_TIMINGR             // If this is variant with TIMINGR register
  __HAL_I2C_ENABLE_IT(ptr_ro_info->ptr_hi2c, I2C_IT_ADDRI | I2C_IT_STOPI | I2C_IT_NACKI | I2C_IT_ERRI);
#else                                   // if this is I2C peripheral without TIMINGR register
  __HAL_I2C_ENABLE_IT(ptr_ro_info->ptr_hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR);
#endif
}

/**
  \fn          void HAL_I2C_ListenCpltCallback (I2C_HandleTypeDef *hi2c)
  \brief       Listen Complete callback.
  \param[in]   hi2c   Pointer to a I2C_HandleTypeDef structure that contains
  *                   the configuration information for the specified I2C
  */
void HAL_I2C_ListenCpltCallback (I2C_HandleTypeDef *hi2c) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }

  // Re-enable listen mode
  (void)HAL_I2C_EnableListen_IT(ptr_ro_info->ptr_hi2c);
}

/**
  \fn          void HAL_I2C_ErrorCallback (I2C_HandleTypeDef *hi2c)
  \brief       I2C error callback.
  \param[in]   hi2c   Pointer to a I2C_HandleTypeDef structure that contains
  *                   the configuration information for the specified I2C
  */
void HAL_I2C_ErrorCallback (I2C_HandleTypeDef *hi2c) {
  const RO_Info_t *ptr_ro_info;
        uint32_t   error;
        uint32_t   event;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  error = HAL_I2C_GetError(hi2c);
  event = ARM_I2C_EVENT_TRANSFER_DONE | ARM_I2C_EVENT_TRANSFER_INCOMPLETE;

  if ((error & HAL_I2C_ERROR_BERR) != 0U) {
    // Bus error
    event |= ARM_I2C_EVENT_BUS_ERROR;
    ptr_ro_info->ptr_rw_info->i2c_bus_error = 1U;
  }

  if ((error & HAL_I2C_ERROR_ARLO) != 0U) {
    // Arbitration lost
    event |= ARM_I2C_EVENT_ARBITRATION_LOST;
    ptr_ro_info->ptr_rw_info->i2c_arbitration_lost = 1U;
  }

  if ((error & HAL_I2C_ERROR_AF) != 0U) {
    // Acknowledge not received
    if ((ptr_ro_info->ptr_hi2c->XferCount == 0U) && (ptr_ro_info->ptr_hi2c->XferSize > 0U)) {
      // Slave address was not acknowledged
      event |= ARM_I2C_EVENT_ADDRESS_NACK;
    }
  }

  if (ptr_ro_info->ptr_rw_info->cb_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_event(event);
  }
}

/**
  \fn          void HAL_I2C_AbortCpltCallback (I2C_HandleTypeDef *hi2c)
  \brief       I2C abort callback.
  \param[in]   hi2c   Pointer to a I2C_HandleTypeDef structure that contains
  *                   the configuration information for the specified I2C
  */
void HAL_I2C_AbortCpltCallback (I2C_HandleTypeDef *hi2c) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = I2C_GetInfo(hi2c);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->xfer_abort = 1U;
}

// Local driver functions definitions (for instances)
#ifdef MX_I2C1
FUNCS_DEFINE(1)
#endif
#ifdef MX_I2C2
FUNCS_DEFINE(2)
#endif
#ifdef MX_I2C3
FUNCS_DEFINE(3)
#endif
#ifdef MX_I2C4
FUNCS_DEFINE(4)
#endif
#ifdef MX_I2C5
FUNCS_DEFINE(5)
#endif
#ifdef MX_I2C6
FUNCS_DEFINE(6)
#endif
#ifdef MX_I2C7
FUNCS_DEFINE(7)
#endif
#ifdef MX_I2C8
FUNCS_DEFINE(8)
#endif

// Global driver structures ****************************************************

#ifdef MX_I2C1
I2C_DRIVER(1)
#endif
#ifdef MX_I2C2
I2C_DRIVER(2)
#endif
#ifdef MX_I2C3
I2C_DRIVER(3)
#endif
#ifdef MX_I2C4
I2C_DRIVER(4)
#endif
#ifdef MX_I2C5
I2C_DRIVER(5)
#endif
#ifdef MX_I2C6
I2C_DRIVER(6)
#endif
#ifdef MX_I2C7
I2C_DRIVER(7)
#endif
#ifdef MX_I2C8
I2C_DRIVER(8)
#endif

#endif  // DRIVER_CONFIG_VALID

/*! \endcond */
